package com.synthefact.daj.model.threaded.impl;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.synthefact.daj.model.NodeIf;
import com.synthefact.daj.model.threaded.ScheduleIf;
import com.synthefact.daj.model.threaded.ThreadedNodeIf;

/**
 * Scheduler for network execution.
 * <p>
 * Copyright (C) 2011 Synthefact
 * <p>
 * Based on code from (c) 1997, Wolfgang Schreiner <Wolfgang.Schreiner@risc.uni-linz.ac.at>
 * http://www.risc.uni-linz.ac.at/software/daj
 */
public class Scheduler implements ScheduleIf {

    final Logger logger = LoggerFactory.getLogger(Scheduler.class);

    /** maximum number of threads */
    private final int nodeMax = 100;

    /** actual number of threads */
    private int nodeNum = 0;

    /** index of currently executing thread; -1 denotes main thread */
    private int threadExec = -1;

    /** main thread */
    private Thread mainThread = null;

    /** node threads to schedule and their status */
    // private NodeIf node[] = new Node[nodeMax];

    private Map<Integer, ThreadedNodeIf> nodeMap = new HashMap<Integer, ThreadedNodeIf>();

    /** an array of booleans indicating if a particular thread is active */
    // private boolean active[] = new boolean[nodeMax];

    /** set if execution is to be interrupted */
    private boolean interruptThread = false;

    /** set if execution has been interrupted */
    private boolean interrupted = false;

    /** set if execution takes place slowly */
    private boolean walking = false;

    /** interrupted thread */
    private Thread currentThread;

    /** scheduling time and counter */
    private int time = 0;

    /** a counter */
    private int counter = 0;

    /** index of next program to execute */
    private int last = -1;

    @Override
    public void main(Thread t) {
	if (mainThread != null) {
	    new IllegalStateException("main thread already set");
	}
	mainThread = t;
    }


    @Override
    public void register(ThreadedNodeIf t) {
	if (nodeNum > nodeMax) {
	    throw new IllegalStateException("too many nodes to schedule");
	}
	nodeMap.put(nodeNum++, t);
    }


    @Override
    public int getNumber() {
	return nodeNum;
    }

    @Override
    public boolean isReady(int i) {
	if (i < 0 || i > nodeNum) {
	    throw new IllegalStateException("invalid thread");
	}
	ThreadedNodeIf node = nodeMap.get(i);
	// XXX probably should just use active
	return node.isActive() && (!node.isReadyToEnd());
    }


    @Override
    public void terminate() {
	ThreadedNodeIf currentNode;
	if (nodeMap.containsKey(threadExec)) {
	    currentNode = nodeMap.get(threadExec);
	    currentNode.setActive(false);
	    // currentNode.setTerminate(true);
	}
    }


    @Override
    public int sleep() {
	ThreadedNodeIf currentNode;
	if (nodeMap.containsKey(threadExec)) {
	    currentNode = nodeMap.get(threadExec);
	    currentNode.setActive(false);
	    // currentNode.setContinue(true);
	}

	schedule();
	return threadExec;
    }


    @Override
    public void interrupt() {
	interruptThread = true;
    }

    @Override
    public void interruptWait() {
	interruptThread = true;
	while (!interrupted)
	    Thread.yield();
    }


    @Override
    public void cont() {
	interruptWait();
	interruptThread = false;
	interrupted = false;
	walking = false;
	synchronized (currentThread) {
	    currentThread.notify();
	}
    }


    @Override
    public void walk() {
	interruptWait();
	interruptThread = false;
	interrupted = false;
	walking = true;
	synchronized (currentThread) {
	    currentThread.notify();
	}
    }


    @Override
    public void step() {
	synchronized (currentThread) {
	    currentThread.notify();
	}
    }


    @Override
    public void awake(int index) {
	ThreadedNodeIf currentNode;
	if (nodeMap.containsKey(index)) {
	    currentNode = nodeMap.get(index);
	    currentNode.setActive(true);
	}
    }


    @Override
    public void exit() {
	interruptThread = false;
	interrupted = false;
	walking = false;
	schedule();
	throw new IllegalStateException("return from scheduler");
    }

    @Override
    public void schedule(int i) {
	logger.debug(String.format("Scheduling: %d", i));
	if (i < -1 || i > nodeNum) {
	    throw new IllegalStateException("invalid node");
	}

	NodeIf n = nodeMap.get(i);
	synchronized (n) {
	    n.notify();
	}
    }


    @Override
    public void schedule() {
	while (!areAllNodesReadyToEnd()) {
	    schedule(nextProgram());
	}
    }


    @Override
    public void incTime() {
	counter++;
	if (counter == nodeNum) {
	    counter = 0;
	    time++;
	}
    }

    @Override
    public int nextProgram() {
	int n = getNumber();
	boolean reset = false;
	do {
	    incTime();
	    last++;
	    if (last == n) {
		last = 0;
		if (reset)
		    return -1;
		reset = true;
	    }
	} while (!isReady(last));
	return last;
    }

    private boolean areAllNodesReadyToEnd() {
	boolean retValue = true;
	for (NodeIf node : nodeMap.values()) {
	    retValue = retValue && node.isReadyToEnd();
	}
	return retValue;
    }
}
